(function() {
  init()

  function init() {
    window.$ = $
    window.$all = $all
    window.animate = animate
    HTMLElement.prototype.$show = show
    HTMLElement.prototype.$hide = hide
    HTMLElement.prototype.$fadeIn = fadeIn
    HTMLElement.prototype.$fadeOut = fadeOut
  }
  
  function $(selector) {
    return document.querySelector(selector)
  }
  
  function $all(selector) {
    return document.querySelectorAll(selector)
  }

  /**
   * @param {number} duration
   * @param {number} from
   * @param {number} to
   * @param {Function} onProgress
   */
  function animate(duration, from, to, onProgress, onFinish) {
    return new Promise(resolve => {
      const dist = to - from
      const speed = dist / duration
      const startTime = Date.now()
      let value = from
      onProgress(value)
      
      function _run() {
        const now = Date.now()
        const time = now - startTime
      
        if (time >= duration) {
          value = to
          onProgress(value)
          onFinish && onFinish()
          resolve()
          return
        }
        const d = time * speed
        value = from + d
        onProgress(value)
      
        requestAnimationFrame(_run)
      }
      
      requestAnimationFrame(_run)
    })
  }

  function fadeIn(duration = 500) {
    return new Promise(resolve => {
      animate(duration, 0, 1, (val) => {
        // this.style.display = 'block'
        this.$show()
        this.style.opacity = val
      }, () => {
        this.style.removeProperty('opacity')
        resolve()
      })
    })
  }

  function fadeOut(duration = 500, onFinish) {
    return new Promise(resolve => {
      animate(duration, 1, 0, (val) => {
        this.style.opacity = val
      }, () => {
        // this.style.display = 'none'
        this.$hide()
        this.style.removeProperty('opacity')
        resolve()
      })
    })
  }
  
  function show() {
    this.style.display = 'block'
  }
  
  function hide() {
    this.style.display = 'none'
  }
})()
